Polygons
In most simulations, such as Global Climate Models, Unstructured Grids are composed of nodes that are connected with edges to form faces that discretize the surface of a sphere (i.e. Earth). For visualization, these faces can be geometrically represented as Polygons and shaded by a corresponding face-centered data variable.
This notebook showcases how UXarray can visualize unstructured grids as polygons.
Warning
This cookbook builds the SpatialPandas library from source (i.e. from the latest commit to Github). If running this notebook locally, please install SpatialPandas using the following command:
pip install git+https://github.com/holoviz/spatialpandas.git
No action is needed if running this cookbook through Binder.
Setup
import uxarray as ux
file_dir = "../../meshfiles/"
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/dask/dataframe/_pyarrow_compat.py:17: FutureWarning: Minimal version of pyarrow will soon be increased to 14.0.1. You are using 12.0.1. Please consider upgrading.
warnings.warn(
Grid with Face-Centered Data Variable
grid_filename_mpas = file_dir + "oQU480.grid.nc"
data_filename_mpas = file_dir + "oQU480.data.nc"
uxds_mpas = ux.open_dataset(grid_filename_mpas, data_filename_mpas)
Grid with Node-Centered Data Variable
grid_filename_geoflow = file_dir + "geoflow.grid.nc"
data_filename_geoflow = file_dir + "geoflow.data.nc"
uxds_geoflow = ux.open_dataset(grid_filename_geoflow, data_filename_geoflow)
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/xarray/core/utils.py:494: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
warnings.warn(
Conversion Methods
UXarray represents Unstructured Grids through a set of coordinate and connectivity variables (i.e. node_lon, node_lat, face_node_connectivity, etc.). These variables can be manipulated to obtain our Polygons for visualuzation
Representation as a GeoDataFrame
Polygons are stored in a SpatialPandas GeoDataFrame, which is the expected data structured by the HoloViz stack of packages for visualizing polygons.
A Grid can be converted into a GeoDataFrame, containing a single “geometry” column, which is a series of Polygons that represent each face.
uxds_mpas.uxgrid.to_geodataframe()
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/xarray/core/utils.py:494: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
warnings.warn(
| geometry | |
|---|---|
| 0 | MultiPolygon([[[-173.4220428466797, 28.4104290... |
| 1 | MultiPolygon([[[-180.0, 87.7088242, -138.95294... |
| 2 | MultiPolygon([[[3.516157388687134, -28.4104290... |
| 3 | MultiPolygon([[[79.46817016601562, -25.8366222... |
| 4 | MultiPolygon([[[-28.531827926635742, 25.836622... |
| ... | ... |
| 1786 | MultiPolygon([[[-102.95294189453125, -50.05697... |
| 1787 | MultiPolygon([[[-102.95294189453125, -52.62263... |
| 1788 | MultiPolygon([[[-171.18515014648438, -53.84706... |
| 1789 | MultiPolygon([[[-178.7207489013672, -53.847068... |
| 1790 | MultiPolygon([[[-180.0, -53.1438933, -178.7207... |
1791 rows × 1 columns
A UxDataArray can also be converted into a GeoDataFrame. It will now have an additional column containing a 1D-slice of data variable.
It’s important to note that to convert a UxDataArray into a GeoDataFrame, the dimension of the data variable must match the number of faces (a.k.a. mapped to faces) and there can not be any additional dimensions (i.e. time, level, etc.)
uxds_mpas["bottomDepth"].to_geodataframe()
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/xarray/core/utils.py:494: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
warnings.warn(
| geometry | bottomDepth | |
|---|---|---|
| 0 | MultiPolygon([[[-173.4220428466797, 28.4104290... | 4973.000000 |
| 1 | MultiPolygon([[[-180.0, 87.7088242, -138.95294... | 4123.000000 |
| 2 | MultiPolygon([[[3.516157388687134, -28.4104290... | 2639.000000 |
| 3 | MultiPolygon([[[79.46817016601562, -25.8366222... | 4001.012148 |
| 4 | MultiPolygon([[[-28.531827926635742, 25.836622... | 5403.000000 |
| ... | ... | ... |
| 1786 | MultiPolygon([[[-102.95294189453125, -50.05697... | 3945.000000 |
| 1787 | MultiPolygon([[[-102.95294189453125, -52.62263... | 4431.000000 |
| 1788 | MultiPolygon([[[-171.18515014648438, -53.84706... | 5197.000000 |
| 1789 | MultiPolygon([[[-178.7207489013672, -53.847068... | 5499.990273 |
| 1790 | MultiPolygon([[[-180.0, -53.1438933, -178.7207... | 4855.000000 |
1791 rows × 2 columns
If a data variable is not face-centered, it can be manipulated to get it to map to faces. For node-centered data, as is the case with our Geoflow dataset, we can perform a nodal-average operation, which takes the average all the nodes that saddle a face and use that value to shade the polygon.
Here we can also see that we need to index the time and meshLayers dimensions to obtain our 1D slice of data.
uxds_geoflow["v1"].nodal_average()[0][0].to_geodataframe()
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/xarray/core/utils.py:494: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
warnings.warn(
| geometry | v1_nodal_average | |
|---|---|---|
| 0 | MultiPolygon([[[0.0, 58.28252410888672, 5.2137... | -0.003357 |
| 1 | MultiPolygon([[[5.213775634765625, 59.79991149... | -0.005317 |
| 2 | MultiPolygon([[[16.497974395751953, 62.0571365... | -0.009873 |
| 3 | MultiPolygon([[[29.138521194458008, 63.2698593... | -0.011219 |
| 4 | MultiPolygon([[[0.0, 61.001914978027344, 5.342... | -0.006898 |
| ... | ... | ... |
| 3835 | MultiPolygon([[[63.31489562988281, -38.6694831... | -0.047156 |
| 3836 | MultiPolygon([[[52.8786506652832, -32.05970001... | -0.513393 |
| 3837 | MultiPolygon([[[55.743770599365234, -32.611831... | -0.398253 |
| 3838 | MultiPolygon([[[61.32698440551758, -33.4846153... | -0.269892 |
| 3839 | MultiPolygon([[[67.02494812011719, -34.1040725... | -0.196878 |
3840 rows × 2 columns
Vector Polygon Plots
UXarray provides the plot.polygons() method, which renders each polygon as a vector graphic using HoloViews.
uxds_mpas["bottomDepth"].plot.polygons(width=900, height=400)
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/uxarray/plot/dataarray_plot.py:400: UserWarning: Including Antimeridian Polygons may lead to visual artifacts. It is suggested to keep 'exclude_antimeridian' set to True.
warnings.warn(
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/xarray/core/utils.py:494: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
warnings.warn(
uxds_geoflow["v1"].nodal_average()[0][0].plot.polygons(
cmap="coolwarm", width=900, height=400
)
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/uxarray/plot/dataarray_plot.py:400: UserWarning: Including Antimeridian Polygons may lead to visual artifacts. It is suggested to keep 'exclude_antimeridian' set to True.
warnings.warn(
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/xarray/core/utils.py:494: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
warnings.warn(
Attention
For moderately to high resolution grids, it’s not recommended to directly plot polygons.
Plotting each polygon is extremely computationally expensive to render and may not provide the most visually-appealing plots.
Rasterized Polygon Plots
Instead of plotting the geometry of each polygon directly, we can rasterize our set of polygons to obtain a raster plot.
Hint
A raster plot of any set of geometric elements (in this case Polygons) renders each data into a regularly shaped array as opposed to rendering each shape directly.
uxds_mpas["bottomDepth"].plot.rasterize(method="polygon", width=900, height=400)
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/xarray/core/utils.py:494: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
warnings.warn(
uxds_geoflow["v1"].nodal_average()[0][0].plot.rasterize(
method="polygon", cmap="coolwarm", width=900, height=400
)
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/xarray/core/utils.py:494: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
warnings.warn(
Handling Antimeridian Polygons
When attempting to visualize unstructured meshes that reside on a sphere, it’s necessary to consider the behavior of geometric elements near the Antimeridian. Elements that exist on or cross the antimeridian need to be corrected to properly visualize them. UXarray uses the antimeridian package to split faces along the antimeridian. More details can be found in their documentation.
![]()
You can select whether to include or exclude these antimeridian polygons by using the exclude_antimeridian parameter.
Attention
For larger, higher-resolution, grids, it’s suggested to keep exclude_antimeridian=True to decrease the time needed to process the grid for visualization.
(
uxds_geoflow["v1"]
.nodal_average()[0][0]
.plot.polygons(
exclude_antimeridian=False,
cmap="coolwarm",
title="With Antimeridian Faces",
width=900,
height=400,
)
+ uxds_geoflow["v1"]
.nodal_average()[0][0]
.plot.polygons(
exclude_antimeridian=True,
cmap="coolwarm",
title="Without Antimeridian Faces",
width=900,
height=400,
)
).cols(1)
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/uxarray/plot/dataarray_plot.py:400: UserWarning: Including Antimeridian Polygons may lead to visual artifacts. It is suggested to keep 'exclude_antimeridian' set to True.
warnings.warn(
/home/runner/miniconda3/envs/unstructured-grid-viz-cookbook-dev/lib/python3.11/site-packages/xarray/core/utils.py:494: FutureWarning: The return type of `Dataset.dims` will be changed to return a set of dimension names in future, in order to be more consistent with `DataArray.dims`. To access a mapping from dimension names to lengths, please use `Dataset.sizes`.
warnings.warn(